//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  The core libraries for the MapWindow 6.0 project.
//
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either expressed or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is MapWindow.dll
//
// The Initial Developer of this Original Code is Ted Dunsford. Created in January, 2008.
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
//
//********************************************************************************************************

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using MapWindow.Data;
using MapWindow.Main;
namespace MapWindow.Drawing
{
    class GridWalls: IRenderable
    {
        private IGraphics _device;
        private bool _isVisible;  
        private ITexture _texture;
        private string _textureFile;
        private Mesh _mesh;
        private IRaster _grid;
        private Array _vertexArray;
        private int _vIndex;
        private bool _useTexture;
        private float _bottomElevation;
        private float _extrusion;
        private Color _color;

        /// <summary>
        /// Occurs immediately after the visible parameter has been adjusted.
        /// </summary>
        public event EventHandler VisibleChanged;

        /// <summary>
        /// This simply performs all the steps to create the grid given the prerequisite components
        /// </summary>
        /// <param name="device">A Direct3D device to draw the sidewalls on</param>
        /// <param name="grid">The IRaster to surround with sidewalls</param>
        /// <param name="TextureFilename">The filename to use for creating the sidewalls</param>
        public GridWalls(IRaster grid, string TextureFilename, IGraphics device):this(grid, TextureFilename, 0f, device)
        {
        }

        /// <summary>
        /// This simply performs all the steps to create the grid given the prerequisite components
        /// </summary>
        /// <param name="device">A Direct3D device to draw the sidewalls on</param>
        /// <param name="grid">The IRaster to surround with sidewalls</param>
        /// <param name="bottomElevation">The bottom elevation for the side walls</param>
        /// <param name="TextureFilename">The filename to use for creating the sidewalls</param>
        public GridWalls(IRaster grid, string TextureFilename, float bottomElevation, IGraphics device)
        {
            _device = device;
            _grid = Grid;
            _textureFile = TextureFilename;
            _texture = device.LoadTexture(_textureFile);
            _useTexture = true;
            _bottomElevation = BottomElevation;
        }

        /// <summary>
        /// This simply performs all the steps to create the grid given the prerequisite components
        /// </summary>
        /// <param name="device">A Direct3D device to draw the sidewalls on</param>
        /// <param name="grid">The IRaster to surround with sidewalls</param>
        /// <param name="color">The color to use for creating the sidewalls</param>
        public GridWalls(IRaster grid, Color color, IGraphics device):this(grid, color, 0f, device)
        {
        }

        /// <summary>
        /// This simply performs all the steps to create the grid given the prerequisite components
        /// </summary>
        /// <param name="device">A Direct3D device to draw the sidewalls on</param>
        /// <param name="grid">The IRaster to surround with sidewalls</param>
        /// <param name="color">The color to use for creating the sidewalls</param>
        /// <param name="bottomElevation">The elevation to draw the bottom at.</param>
        public GridWalls(IRaster grid, Color color, float bottomElevation, IGraphics device)
        {
            _color = color;
            _device = device;
            _grid = grid;
            _useTexture = false;
            _bottomElevation = bottomElevation;
        }

       


        #region Public Properties

        /// <summary>
        /// Gets or sets the elevation that should be used to create the side walls.  
        /// The CreateMesh function should be called after this is changed.
        /// </summary>
        public float BottomElevation
        {
            get { return _bottomElevation; }
            set { _bottomElevation = value; }
        }

        /// <summary>
        /// Stores the graphics device which is necessary for mesh creation.
        /// This should be set before setting the texture.
        /// </summary>
        public Device Device
        {
            get { return _device; }
            set { _device = value; }
        }

        /// <summary>
        /// Gets or sets the Extrusion.  To show correctly in 3D, distance units must be converted from
        /// their natural values (centemeters, for instance) into the same coordinate system being used
        /// for X, Y values, like decimal degrees.
        /// </summary>
        public float Extrusion
        {
            get { return _extrusion; }
            set { _extrusion = value; }
        }

        /// <summary>
        /// This is the grid that we are going to surround.  
        /// This should be set before setting the texture.
        /// </summary>
        public IRaster Grid
        {
            get { return _grid; }
            set { 
                _grid = value;
                CreateMesh(); // Recalculate the new coordinates with the new grid
            }
        }

        /// <summary>
        /// Gets the mesh for the sidewalls
        /// </summary>
        public Mesh Mesh
        {
            get { return _mesh; }
        }
       

        /// <summary>
        /// Resetting this must also be followed by a reset grid call
        /// </summary>
        public Texture Texture
        {
            get { return _texture; }
            set {
                if (_texture != null) _texture.Dispose();
                _texture = value;
                
            }
        }

        /// <summary>
        /// Specifies a file to load the texture from.
        /// This is the last thing that should be specified, and doing so will actually build the grid.
        /// </summary>
        public string TextureFile
        {
            get { return _textureFile; }
            set {
                _textureFile = value;
                // By calling this, we force the process to set the texture as well
                _texture = TextureLoader.FromFile(_device, _textureFile);
            }
        }

        /// <summary>
        /// Determines whether or not to show the sidewalls.  This defaults to false.
        /// </summary>
        public bool IsVisible
        {
            get { return _isVisible; }
            set { _isVisible = value; }
        }

        #endregion 


        /// <summary>
        /// This draws the 4 vertical surfaces around the grid.
        /// This requires the existance of both the device and a grid to surround
        /// </summary>
        public void CreateMesh()
        {
            if (_grid == null) return;
            if (_device == null) return;
         
            // Elevation bitmap
            int width = _grid.NumColumns;
            int height = _grid.NumRows;

       
            // Mesh values
            int numVertices = (width + height) * 2 * 2;
            int numFaces = (width - 1 + height - 1) * 2 * 2;
            int indexCount = numFaces * 3;
            int[] ranks = new int[1];
            

            if (_useTexture == true)
            {
                // Create mesh
                _mesh = new Mesh(numFaces, numVertices, MeshFlags.Managed, CustomVertex.PositionNormalTextured.Format, _device);
                ranks[0] = _mesh.NumberVertices;
                _vertexArray = _mesh.VertexBuffer.Lock(0, typeof(CustomVertex.PositionNormalTextured), LockFlags.None, ranks);
            }
            else
            {
                // Create mesh
                _mesh = new Mesh(numFaces, numVertices, MeshFlags.Managed, CustomVertex.PositionColored.Format, _device);
                ranks[0] = _mesh.NumberVertices;
                _vertexArray = _mesh.VertexBuffer.Lock(0, typeof(CustomVertex.PositionColored), LockFlags.None, ranks);
            }
            // Set the vertex buffer
            _vIndex = 0;
            int x, y;
           
            // North side
            y = 0;
            for (x = 0; x < width; x++)
            { 
                AddPoint(x, y);
            }

            // East side
            x = width - 1;
            for (y = 0; y < height; y++)
            {
                AddPoint(x, y);
            }

            // South side reversed
            y = height - 1;
            for (x = width - 1; x >= 0; x--)
            {
                AddPoint(x, y);
            }

            // West side reversed
            x = 0;
            for (y = height - 1; y >= 0; y--)
            {
                AddPoint(x, y);
            }
            _mesh.VertexBuffer.Unlock();

            // Set index buffer
            ranks[0] = indexCount;
            _vertexArray = _mesh.LockIndexBuffer(typeof(short), LockFlags.None, ranks);
            int i = 0;
            short bottomVertex = 0;
            short topVertex = 0;
            // North
            topVertex = (short)0;
            bottomVertex = (short)(topVertex + 1);
            for (x = 0; x < width - 1; x++)
            {
                _vertexArray.SetValue(topVertex, i++);
                _vertexArray.SetValue(bottomVertex, i++);
                _vertexArray.SetValue((short)(bottomVertex + 2), i++);
                _vertexArray.SetValue(topVertex, i++);
                _vertexArray.SetValue((short)(bottomVertex + 2), i++);
                _vertexArray.SetValue((short)(topVertex + 2), i++);
                bottomVertex += 2;
                topVertex += 2;
            }
            // East
            topVertex = (short)(width * 2);
            bottomVertex = (short)(topVertex + 1);
            for (y = 0; y < height - 1; y++)
            {
                _vertexArray.SetValue(topVertex, i++);
                _vertexArray.SetValue(bottomVertex, i++);
                _vertexArray.SetValue((short)(bottomVertex + 2), i++);
                _vertexArray.SetValue(topVertex, i++);
                _vertexArray.SetValue((short)(bottomVertex + 2), i++);
                _vertexArray.SetValue((short)(topVertex + 2), i++);
                bottomVertex += 2;
                topVertex += 2;
            }
            // South
            topVertex = (short)((width + height) * 2);
            bottomVertex = (short)(topVertex + 1);
            for (x = 0; x < width - 1; x++)
            {
                _vertexArray.SetValue(topVertex, i++);
                _vertexArray.SetValue(bottomVertex, i++);
                _vertexArray.SetValue((short)(bottomVertex + 2), i++);
                _vertexArray.SetValue(topVertex, i++);
                _vertexArray.SetValue((short)(bottomVertex + 2), i++);
                _vertexArray.SetValue((short)(topVertex + 2), i++);
                bottomVertex += 2;
                topVertex += 2;
            }
            // West
            topVertex = (short)((width + height + width) * 2);
            bottomVertex = (short)(topVertex + 1);
            for (y = 0; y < height - 1; y++)
            {
                _vertexArray.SetValue(topVertex, i++);
                _vertexArray.SetValue(bottomVertex, i++);
                _vertexArray.SetValue((short)(bottomVertex + 2), i++);
                _vertexArray.SetValue(topVertex, i++);
                _vertexArray.SetValue((short)(bottomVertex + 2), i++);
                _vertexArray.SetValue((short)(topVertex + 2), i++);
                bottomVertex += 2;
                topVertex += 2;
            }
            _mesh.IndexBuffer.SetData(_vertexArray, 0, LockFlags.None);
            //_mesh.ComputeNormals();

        
        }

        /// <summary>
        /// Actually draws the walls around the grid.
        /// </summary>
        /// <param name="device">The Microsoft.DirectX.Direct3D.Device to draw to</param>
        public void Draw(IGraphics device)
        {
            Draw(device, null);
        }

        /// <summary>
        /// Actually draws the walls around the grid.
        /// </summary>
        /// <param name="device">The Microsoft.DirectX.Direct3D.Device to draw to</param>
        /// <param name="progressHandler">Any valid implementation of the IProgressHandler interface</param>
        public void Draw(IGraphics device, IProgressHandler progressHandler)
        {
            if (_isVisible == true)
            {
                if (_useTexture == true)
                {
                    if (_texture != null && _mesh != null)
                    {
                        _device.SetTexture(0, _texture);
                        _mesh.DrawSubset(0);
                    }
                }
                else
                {
                    if (_mesh != null)
                    {
                        _mesh.DrawSubset(0);
                    }
                }
              
            }
        }

        /// <summary>
        /// This actually adds two points to the total system, one for the top, one for the bottom
        /// </summary>
        /// <param name="col">The X coordinate</param>
        /// <param name="row">The Y coordinate</param>
        private void AddPoint(int col, int row)
        {
            float x = Convert.ToSingle(col * _grid.CellWidth + _grid.Xllcenter);
            float y = Convert.ToSingle(-row * _grid.CellHeight + _grid.Yllcenter + _grid.CellHeight * _grid.NumRows);
            if (_useTexture == true)
            {
                CustomVertex.PositionNormalTextured pnt;
                pnt = new CustomVertex.PositionNormalTextured(); // Terrain Level
                pnt.X = x;
                pnt.Y = y;
                float z = Convert.ToSingle(_grid.Value[row, col]) - Convert.ToSingle(_grid.Minimum) + _bottomElevation;
                pnt.Z = -z * _extrusion;
                pnt.Tu = 0f;
                pnt.Tv = z / (Convert.ToSingle(_grid.Maximum) - Convert.ToSingle(_grid.Minimum));
                _vertexArray.SetValue(pnt, _vIndex++);
                pnt = new CustomVertex.PositionNormalTextured(); // Bottom Level
                pnt.X = x;
                pnt.Y = y;
                pnt.Z = _bottomElevation;
                pnt.Tu = 0f;
                pnt.Tv = 0f;
                _vertexArray.SetValue(pnt, _vIndex++);
            }
            else
            {
                CustomVertex.PositionColored pnt;
                
                pnt = new CustomVertex.PositionColored(); // Terrain Level
                pnt.X = x;
                pnt.Y = y;
                float z = System.Convert.ToSingle(_grid.Value[row, col]) - Convert.ToSingle(_grid.Minimum);  
                pnt.Z = -z * _extrusion;
                pnt.Color = _color.ToArgb();
                _vertexArray.SetValue(pnt, _vIndex++);

                pnt = new CustomVertex.PositionColored(); // Bottom Level
                pnt.X = x;
                pnt.Y = y;
                pnt.Z = 0f;
                pnt.Color = _color.ToArgb();
                _vertexArray.SetValue(pnt, _vIndex++);
            }
        }



        public void Dispose()
        {
            _texture.Dispose();
            _mesh.Dispose();
           
        }

        #region IRenderable3D Members

        


       

        /// <summary>
        /// Gets the envelope of the raster that created these sidewalls
        /// </summary>
        public MapWindow.Geometries.IEnvelope Envelope
        {
            get 
            {
                if (_grid == null) return null;
                return _grid.Bounds.Envelope;
            }
        }

        #region Draw Events

        /// <summary>
        /// Occurs when the Draw method is called and provides the option to cancel the event before anything is actually drawn.
        /// </summary>
        public virtual event EventHandler<DrawCancel> BeforeDrawing;

        /// <summary>
        /// Fires the event that occurs before drawing to a directX map
        /// </summary>
        /// <param name="device">A DirectX.Direct3D.Device that will be drawn to</param>
        /// <returns>Boolean, that if true should cancel the draw event.</returns>
        protected virtual bool OnBeforeDrawing(Device device)
        {
            if (BeforeDrawing == null) return false;
            DrawCancel e = new DrawCancel(device);
            BeforeDrawing(this, e);
            return e.Cancel;
        }


        /// <summary>
        /// Occurs after the contents of this MapLayerCollection have been rendered to the Microsoft.DirectX.Direct3D.Device
        /// </summary>
        public virtual event EventHandler<DrawCompleted> AfterDrawing;

        /// <summary>
        /// Fires an event when we are done with the drawing code, whether the drawing was successful, cancelled, or threw an exception.
        /// </summary>
        /// <param name="device">A DirectX.Direct3D.Device that rendering took place on</param>
        /// <param name="cancelled">If true, the event was cancelled normally</param>
        /// <param name="ex">An Exception in the event that this was an error.  This will normally be null.</param>
        protected virtual void OnAfterDrawing(Device device, bool cancelled, Exception ex)
        {
            if (AfterDrawing == null) return;
            DrawCompleted e;
            if (ex == null)
            {
                e = new DrawCompleted(device, cancelled);
            }
            else
            {
                e = new DrawCompleted(device, ex);
            }
            AfterDrawing(this, e);
        }


        #endregion
        /// <summary>
        /// Fires the Visible Changed event
        /// </summary>
        protected virtual void OnVisibleChanged()
        {
            VisibleChanged(this, new EventArgs());
        }

        #endregion
    }
}
